优化代码 创建基于 CASL 的策略权限控制服务
将前几节的测试代码重构为生产级服务,提炼出统一的 buildAbility 工厂方法和三种策略处理器(handleJsonType / handleMongoType / handleFunctionType),通过 IPolicy 接口规范数据结构,实现一个可扩展的策略权限控制服务。
IPolicy 接口定义
export interface IPolicy {
/** 类型标识: 0=JSON, 1=MongoDB查询, 2=函数 */
type: number;
/** 判断字段: 'can' | 'cannot' */
effect: 'can' | 'cannot';
/** 操作标识: create, read, update, delete, manage 等 */
action: string;
/** 资源标识: 类名,如 'Article', 'User' */
subject: string;
/** 允许操作的字段,可选 */
fields?: string | string[];
/** 条件: 对象或函数体字符串 */
conditions?: string | Record<string, any>;
/** 函数参数(仅 type=2 时使用) */
args?: string | string[];
}
typescript
AbilityType 类型定义
import {
MongoAbility,
MongoQuery,
PureAbility,
AbilityTuple,
MatchConditions,
} from '@casl/ability';
export type AbilityType =
| MongoAbility<MongoQuery>
| PureAbility<AbilityTuple, MatchConditions>;
typescript
buildAbility 工厂方法
import { Injectable } from '@nestjs/common';
import {
AbilityBuilder,
createMongoAbility,
PureAbility,
buildMongoQueryMatcher,
} from '@casl/ability';
import type { MatchConditions } from '@casl/ability';
import { allParsingInstructions, allInterpreters } from '@ucast/mongo2js';
@Injectable()
export class CaslAbilityService {
/**
* 核心:根据 Policy 数组构建 ability 实例数组
*/
buildAbility(policies: IPolicy[], args?: any): AbilityType[] {
const abilityArr: AbilityType[] = [];
policies.forEach((policy) => {
let ability: AbilityType;
switch (policy.type) {
case 0:
ability = this.handleJsonType(policy);
break;
case 1:
ability = this.handleMongoType(policy);
break;
case 2:
ability = this.handleFunctionType(policy, args);
break;
default:
ability = this.handleJsonType(policy);
}
abilityArr.push(ability);
});
return abilityArr;
}
}
typescript
三种策略处理器实现
handleJsonType -- 基础 JSON 条件
private handleJsonType(policy: IPolicy): AbilityType {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
const action = this.determineAction(policy.effect, can, cannot);
// conditions 可能是 JSON 字符串,需要解析
let conditions = typeof policy.conditions === 'string'
? JSON.parse(policy.conditions || '{}')
: policy.conditions;
conditions = conditions || {};
action(policy.action, policy.subject, policy.fields, conditions);
return build();
}
typescript
handleMongoType -- MongoDB 查询操作符
private handleMongoType(policy: IPolicy): AbilityType {
const { can, cannot, build } = new AbilityBuilder(createMongoAbility);
const conditionsMatcher = buildMongoQueryMatcher(
allParsingInstructions,
allInterpreters
);
const action = this.determineAction(policy.effect, can, cannot);
let conditions = typeof policy.conditions === 'string'
? JSON.parse(policy.conditions || '{}')
: policy.conditions;
conditions = conditions || {};
action(policy.action, policy.subject, conditions);
return build({ conditionsMatcher });
}
typescript
handleFunctionType -- 函数条件
private handleFunctionType(policy: IPolicy, args: any): AbilityType {
const { can, cannot, build } = new AbilityBuilder(PureAbility);
const conditionsMatcher: MatchConditions = (match) => match;
const action = this.determineAction(policy.effect, can, cannot);
// 从 args 和 conditions 构建动态函数
let fn: Function;
if (policy.args && (policy.args as string[]).length > 0) {
const arr = typeof policy.args === 'string'
? policy.args.split(',')
: policy.args;
fn = new Function(...arr, `return ${policy.conditions}`);
} else {
fn = new Function(`return ${policy.conditions}`);
}
action(policy.action, policy.subject, fn(...[args]));
return build({ conditionsMatcher });
}
typescript
公共方法:determineAction
private determineAction(
effect: string,
can: any,
cannot: any
): any {
return effect === 'can' ? can : cannot;
}
typescript
三种处理器的对比
| 处理器 | Ability 类型 | conditionsMatcher | conditions 输入 | 适用场景 |
|---|---|---|---|---|
| handleJsonType | createMongoAbility | 默认 | JSON 对象/字符串 | { username: "tom1" } |
| handleMongoType | createMongoAbility | buildMongoQueryMatcher | JSON 对象含操作符 | { $or: [...] } |
| handleFunctionType | PureAbility | MatchConditions | 函数体字符串 | "authorId === user.id" |
模块注册
// policy.module.ts
import { Module } from '@nestjs/common';
import { CaslAbilityService } from './casl-ability/casl-ability.service';
@Module({
providers: [CaslAbilityService],
exports: [CaslAbilityService],
})
export class PolicyModule {}
typescript
使用 Guard 时在对应模块中导入 PolicyModule 即可注入 CaslAbilityService。
至此,CaslAbilityService 的核心代码重构完成。下一节将在 PolicyGuard 中通过 buildAbility 方法进行集成测试,验证三种策略的端到端流程。
↑